/**
 * Copyright (c) 2021-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/ecma_language_context-inl.h"
#include "plugins/ecmascript/runtime/ecma_language_context.h"

#include "plugins/ecmascript/compiler/ecmascript_extensions/ecmascript_environment.h"
#include "plugins/ecmascript/runtime/ecma_class_linker_extension.h"
#include "plugins/ecmascript/runtime/class_linker/program_object.h"
#include "plugins/ecmascript/runtime/ecma_exceptions.h"
#include "plugins/ecmascript/runtime/base/error_type.h"
#include "plugins/ecmascript/runtime/js_method.h"
#include "plugins/ecmascript/runtime/js_object.h"
#include "plugins/ecmascript/runtime/js_function.h"
#include "plugins/ecmascript/runtime/js_tagged_value.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/runtime/lexical_env.h"
#include "plugins/ecmascript/runtime/interpreter/js_frame.h"
#include "include/method.h"
#include "runtime/core/core_itable_builder.h"
#include "runtime/core/core_vtable_builder.h"

namespace ark {
using ecmascript::EcmaVM;
using ecmascript::JSThread;

std::pair<Method *, uint32_t> EcmaLanguageContext::GetCatchMethodAndOffset(Method *method, ManagedThread *thread) const
{
    Method *catchMethod = method;
    uint32_t catchOffset = 0;
    auto jsThread = static_cast<JSThread *>(thread);
    for (auto stack = StackWalker::Create(thread); stack.HasFrame(); stack.NextFrame()) {
        catchMethod = stack.GetMethod();
        if (catchMethod->IsNative()) {
            continue;
        }
        catchOffset = catchMethod->FindCatchBlock(jsThread->GetException().GetTaggedObject()->ClassAddr<Class>(),
                                                  stack.GetBytecodePc());
        if (catchOffset != panda_file::INVALID_OFFSET) {
            break;
        }
    }

    return std::make_pair(catchMethod, catchOffset);
}

PandaVM *EcmaLanguageContext::CreateVM(Runtime *runtime, const RuntimeOptions &options) const
{
    auto ret = EcmaVM::Create(runtime, ecmascript::JSRuntimeOptions(options));
    if (ret) {
        return ret.Value();
    }
    return nullptr;
}

std::unique_ptr<ClassLinkerExtension> EcmaLanguageContext::CreateClassLinkerExtension() const
{
    return std::make_unique<ecmascript::EcmaClassLinkerExtension>();
}

void EcmaLanguageContext::ThrowException(ManagedThread *thread, const uint8_t *mutf8Name, const uint8_t *mutf8Msg) const
{
    ecmascript::ThrowException(JSThread::Cast(thread), reinterpret_cast<const char *>(mutf8Name),
                               reinterpret_cast<const char *>(mutf8Msg));
}

void EcmaLanguageContext::ThrowStackOverflowException(ManagedThread *thread) const
{
    ecmascript::ThrowException(JSThread::Cast(thread), ecmascript::RANGE_ERROR_STRING,
                               "Maximum call stack size exceeded");
}

PandaUniquePtr<ITableBuilder> EcmaLanguageContext::CreateITableBuilder(
    [[maybe_unused]] ClassLinkerErrorHandler *errHandler) const
{
    return MakePandaUnique<CoreITableBuilder>();
}

PandaUniquePtr<VTableBuilder> EcmaLanguageContext::CreateVTableBuilder(ClassLinkerErrorHandler *errHandler) const
{
    return MakePandaUnique<CoreVTableBuilder>(errHandler);
}

size_t EcmaLanguageContext::GetStringSize(const ObjectHeader *stringObject) const
{
    auto string = static_cast<const ecmascript::EcmaString *>(stringObject);
    return string->ObjectSize();
}

using VRegType = ark::compiler::VRegInfo::VRegType;
void EcmaLanguageContext::RestoreEnv(Frame *currentIframe, const StackWalker::EnvData &envData) const
{
    auto *iframeEnv = ecmascript::JSExtFrame::FromFrame(currentIframe)->GetExtData();
    iframeEnv->SetThisFunc(
        ecmascript::JSFunction::Cast(ecmascript::JSTaggedValue(envData[VRegType::THIS_FUNC]).GetHeapObject()));
    iframeEnv->SetConstantPool(ecmascript::ConstantPool::Cast(envData[VRegType::CONST_POOL]));
    iframeEnv->SetLexicalEnv(ecmascript::LexicalEnv::Cast(envData[VRegType::LEX_ENV]));
    // restore func parameter
    auto numVregs = currentIframe->GetMethod()->GetNumVregs();
    DynamicFrameHandler frameHandler(currentIframe);
    frameHandler.GetVReg(numVregs + ecmascript::js_method_args::FUNC_IDX).SetValue(envData[VRegType::THIS_FUNC]);
}

void EcmaLanguageContext::InitializeOsrCframeSlots(Span<uintptr_t> paramSlots) const
{
    std::fill(paramSlots.begin(), paramSlots.end(), TaggedValue::VALUE_UNDEFINED);
}

uint64_t EcmaLanguageContext::GetOsrEnv(const Frame *iframe, compiler::VRegInfo vregInfo) const
{
    auto env = ecmascript::JSFrame::GetJSEnv(iframe);
    switch (vregInfo.GetVRegType()) {
        case compiler::VRegInfo::VRegType::THIS_FUNC:
            return TaggedValue(env->GetThisFunc()).GetRawData();
        case compiler::VRegInfo::VRegType::CONST_POOL:
            return TaggedValue(env->GetConstantPool()).GetRawData();
        case compiler::VRegInfo::VRegType::LEX_ENV:
            return TaggedValue(env->GetLexicalEnv()).GetRawData();
        default:
            UNREACHABLE();
            return 0;
    }
}

}  // namespace ark
